iT邦幫忙

0

# Day 05使用 Pydantic 定義餐飲結構與實作推薦模擬 API

n8n
  • 分享至 

  • xImage
  •  

在昨天的文章中,我們順利架設了 FastAPI 微服務專案的骨架,完成了系統健康檢查端點,並驗證了 Swagger UI 自動生成文件的便利性。

既然我們的目標是打造一個專屬的 美食餐飲推薦 Agent,那麼後端的核心工作之一,就是確保流經系統的資料結構明確且嚴謹。今天我們將利用 FastAPI 內建的強大型別工具 Pydantic,定義使用者的點餐需求與餐廳餐點的模型結構(Schema),並實作第一隻模擬推薦 API!


一、為什麼強烈推薦使用 Pydantic?

在串接 AI Agent 與外部應用(如 LINE Bot、Webhook)時,使用者輸入的資料經常五花八門(例如忘記帶預算、字串格式不對、缺漏必填欄位等)。Pydantic 具備以下優勢:

  1. 嚴格的型別校驗與自動轉換:例如前端送過來字串 "200",Pydantic 能自動轉換為整數 200,格式不符則即時拋出清楚的 422 錯誤。
  2. 預設值與欄位約束:可透過 Field 輕鬆設定最小值、最大值、長度限制與預設值。
  3. 無縫整合 OpenAPI:定義好的模型與範例資料,會自動呈現在 /docs 互動文件上,利於除錯與後續團隊協作。

二、規劃美食推薦資料模型 (Schemas)

我們在 app 資料夾下新增一個 schemas 模組,專門管理所有資料結構定義。

Step 1:建立檔案目錄

請確認當前虛擬環境已啟用,目錄結構新增如下:

agent-backend/
├── app/
│   ├── schemas/
│   │   ├── __init__.py
│   │   └── food.py      # 美食與推薦相關的 Pydantic 模型

Step 2:定義資料模型 (app/schemas/food.py)

建立 app/schemas/food.py,定義三個關鍵模型:

UserPreference:使用者的偏好與查詢條件(預算、飲食偏好、所在區域等)。

RestaurantItem:單一餐廳或餐點的詳細資訊。

RecommendationResponse:最終回傳給使用者或 Agent 的推薦清單。

from pydantic import BaseModel, Field
from typing import List, Optional
from enum import Enum

class CuisineType(str, Enum):
    ALL = "全部"
    JAPANESE = "日式"
    KOREAN = "韓式"
    TAIWANESE = "台式"
    DESSERT = "甜點/下午茶"
    CAFE = "咖啡廳"
    WESTERN = "西式"

class UserPreference(BaseModel):
    user_id: str = Field(..., description="使用者唯一識別碼", example="user_12345")
    cuisine: CuisineType = Field(default=CuisineType.ALL, description="料理類型")
    budget_max: int = Field(default=500, ge=50, le=5000, description="每人預算上限 (50~5000 元)")
    location: str = Field(default="板橋區", description="目標搜尋區域或商圈")
    notes: Optional[str] = Field(None, description="特殊飲食備註,例如:不吃香菜、要甜點、寵物友善")

class RestaurantItem(BaseModel):
    id: str = Field(..., description="餐廳唯一識別碼")
    name: str = Field(..., description="餐廳名稱")
    cuisine: CuisineType = Field(..., description="料理分類")
    price_avg: int = Field(..., description="平均每人消費")
    location: str = Field(..., description="所在地址或區域")
    rating: float = Field(..., ge=1.0, le=5.0, description="Google 評分 (1.0~5.0)")
    signature_dish: str = Field(..., description="招牌餐點")
    reason: Optional[str] = Field(None, description="推薦理由")

class RecommendationResponse(BaseModel):
    status: str = Field(default="success")
    total_found: int = Field(..., description="符合條件的數量")
    recommendations: List[RestaurantItem] = Field(..., description="推薦餐廳清單")

三、實作推薦 API 路由 (app/routers/recommendation.py)

有了 Schema 之後,我們來實作模擬推薦端點。這裡先準備一批精選的模擬餐廳資料庫(Mock DB),並根據使用者的偏好進行過濾篩選。

建立 app/routers/recommendation.py:

from fastapi import APIRouter, HTTPException
from typing import List
from app.schemas.food import UserPreference, RestaurantItem, RecommendationResponse, CuisineType

router = APIRouter(
    prefix="/api/v1/recommend",
    tags=["Food Recommendation"]
)

模擬店家資料庫

MOCK_RESTAURANTS: List[RestaurantItem] = [
    RestaurantItem(
        id="rest_01",
        name="恬淡日常法式甜點",
        cuisine=CuisineType.DESSERT,
        price_avg=280,
        location="板橋區",
        rating=4.7,
        signature_dish="草莓戚風蛋糕、伯爵生乳捲",
        reason="巷弄隱密甜點店,氣氛溫馨悠閒,適合放鬆聊天。"
    ),
    RestaurantItem(
        id="rest_02",
        name="炙燒極味鐵板燒",
        cuisine=CuisineType.TAIWANESE,
        price_avg=320,
        location="江子翠",
        rating=4.5,
        signature_dish="香煎比目魚、起司牛排卷",
        reason="江子翠捷運站旁平價鐵板燒首選,師傅現炒香氣十足。"
    ),
    RestaurantItem(
        id="rest_03",
        name="沐木手沖咖啡館",
        cuisine=CuisineType.CAFE,
        price_avg=190,
        location="台中市西區",
        rating=4.8,
        signature_dish="淺焙花香手沖、焦糖肉桂卷",
        reason="採光極佳的老宅選店,甜點與單品咖啡皆在水準之上。"
    ),
    RestaurantItem(
        id="rest_04",
        name="春日豚骨拉麵",
        cuisine=CuisineType.JAPANESE,
        price_avg=260,
        location="新莊區",
        rating=4.6,
        signature_dish="特濃黑蒜油豚骨拉麵",
        reason="湯頭濃郁甘醇,麵條硬度與濃淡可自由客製。"
    ),
]

@router.post("/", response_model=RecommendationResponse)
async def recommend_food(preference: UserPreference):
    
    根據使用者輸入的預算、料理偏好與區域,過濾並回傳推薦餐廳清單
    
    matched_results: List[RestaurantItem] = []

    for item in MOCK_RESTAURANTS:
        # 1. 預算過濾
        if item.price_avg > preference.budget_max:
            continue
        
        # 2. 料理類型過濾 (若非 ALL 且不符則略過)
        if preference.cuisine != CuisineType.ALL and item.cuisine != preference.cuisine:
            continue
        
        # 3. 區域模糊比對 (若設定的區域與餐廳區域不匹配)
        if preference.location not in item.location and item.location not in preference.location:
            continue
        
        matched_results.append(item)

    # 若找不到符合條件的店家,回傳自訂訊息或拋出提示
    return RecommendationResponse(
        status="success",
        total_found=len(matched_results),
        recommendations=matched_results
    )

四、掛載路由至主程式 (app/main.py)

修改 app/main.py,將新建好的 recommendation.py 路由掛載至 FastAPI 應用實例:

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.routers import health, recommendation

app = FastAPI(
    title="Food Agent Microservice API",
    description="智慧餐飲推薦與工作流核心後端服務",
    version="1.0.0"
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 註冊路由
app.include_router(health.router)
app.include_router(recommendation.router)

@app.get("/")
async def root():
    return {
        "message": "Welcome to Food Agent API",
        "docs_url": "/docs"
    }

五、Swagger UI 測試與端點驗收

確認 Uvicorn 伺服器仍在執行中(或於終端機執行 uvicorn app.main:app --reload):

1.打開瀏覽器訪問 http://localhost:8000/docs。

2.在 Food Recommendation 分組下展開 POST /api/v1/recommend/。

3.點擊 Try it out,輸入測試 JSON 參數(例如想在板橋找 400 元內的甜點店):

{
  "user_id": "wanglulu_01",
  "cuisine": "甜點/下午茶",
  "budget_max": 400,
  "location": "板橋",
  "notes": "想找適合放鬆的安靜甜點店"
}

1.點擊 Execute,確認伺服器回傳 200 OK,並精準過濾出「恬淡日常法式甜點」:

{
  "status": "success",
  "total_found": 1,
  "recommendations": [
    {
      "id": "rest_01",
      "name": "恬淡日常法式甜點",
      "cuisine": "甜點/下午茶",
      "price_avg": 280,
      "location": "板橋區",
      "rating": 4.7,
      "signature_dish": "草莓戚風蛋糕、伯爵生乳捲",
      "reason": "巷弄隱密甜點店,氣氛溫馨悠閒,適合放鬆聊天。"
    }
  ]
}

今日結語與明日預告
今天我們透過 Pydantic 定義出了嚴謹的餐飲推薦資料規範,並在 FastAPI 中實作了具備篩選邏輯的推薦端點。這代表我們的後端系統已經具備了真正的商業邏輯運算能力!

明天 Day 06,我們將進入下一個重頭戲:串聯自動化流程!在 n8n 中調用 FastAPI 推薦端點,實現多系統間的資料流轉,敬請期待!


*提醒邦友,使用第三方服務/API 時,請務必評估資安風險與隱私保護
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言